#pragma once
#include <cstdlib>
#include <type_traits>
#include <exception>
#include <iostream>

template <typename T>
class MyAllocator
{
  public:
    using value_type = T;
    using pointer = T*;
    using const_pointer = const T*;
    using void_pointer = void*;
    using const_void_pointer = const void*;
    using difference_type = std::ptrdiff_t;
    using size_type = std::size_t;
    // stateless allocator to be always equal
    using is_always_equal = std::true_type;

    MyAllocator()
    {
        std::cout << this << " constructor, sizeof(T): " << sizeof(T) << '\n';
    }

    // A container has its own internal data structures that must also be
    // allocated. This is because when you use the allocator with a container,
    // you specify the type in the container's definition. But the container has
    // its own internal data structures that must also be allocated. The
    // container will create a copy of the given allocator using the template
    // version of the copy constructor, providing itself with the ability to
    // create its own node allocator using the allocator that it was originally
    // provided. The template version of the copy constructor is required for a
    // fully functional allocator.
    template <typename U>
    MyAllocator(const MyAllocator<U>& other) noexcept
    {
        (void)other;
    }

    pointer allocate(size_type n)
    {
        if (auto ptr = static_cast<pointer>(malloc(sizeof(T) * n)))
        {
            std::cout << this << " allocates [" << n << "]: " << ptr << "\n";
            return ptr;
        }
        throw std::bad_alloc{};
        return nullptr;
    }

    // Since the allocator is stateless, the ptr does not neet to originate from
    // the same allocator performing the deallocation.
    // Since we are using malloc and free, they automatically keep track of the
    // size of the original allocation for us.
    void deallocate(pointer p, size_type n)
    {
        (void)n;
        std::cout << this << " deallocates [" << n << "]: " << p << '\n';
        free(p);
    }
};

template <typename T1, typename T2>
bool operator==(const MyAllocator<T1>& lhs, const MyAllocator<T2>& rhs)
{
    return true;
}

template <typename T1, typename T2>
bool operator!=(const MyAllocator<T1>& lhs, const MyAllocator<T2>& rhs)
{
    return true;
}

/**
 * @brief example:
 * Since the allocator is stateless, you can allocate a pointer from one
 * allocator and deallocate this pointer from another allocator with same type
 *
 * myallocator<int> myalloc1;
 * myallocator<int> myalloc2;
 *
 * auto ptr = myalloc1.allocate(1);
 * myalloc2.deallocate(ptr, 1);
 */